/*
 * BlackLightning.PluginLib.Modules.Packets.ExceptionPacket
 * Andy Tidball
 * 
 * Project: Black Lightning Plugin Library
 * Copyright: GNU General Public License
 */

using System;
using System.Text;

namespace BlackLightning.PluginLib.Modules.Packets {
    /// <summary>
    /// A packet sent to the front end to display an error.
    /// </summary>
    [Serializable]
    public class ExceptionPacket : BLPacket {
        // member data
        private Exception _Exception    = null;  // the exception to dispay in the front end
        private bool      _MessagesOnly = false; // whether or not to limit the output to just the exception's message(s)

        /// <summary>
        /// Creates a new ExceptionPacket.
        /// </summary>
        /// <param name="x">The exception to format and send to the front end.</param>
        public ExceptionPacket(Exception x)
            : this(x, false) {
        }

        /// <summary>
        /// Creates a new ExceptionPacket.
        /// </summary>
        /// <param name="x">The exception to format and send to the front end.</param>
        /// <param name="MessagesOnly">Whether or not to only include the only exception messages, or to include other data as well.</param>
        public ExceptionPacket(Exception x, bool MessagesOnly)
            : base(Program.BlackLightning, Program.FrontEnd, null) {
            _Exception    = x;
            _MessagesOnly = MessagesOnly;
        }

        /// <summary>
        /// Gets the exception that this packet is formatting and sending.
        /// </summary>
        public Exception Exception {
            get {
                return _Exception;
            }
        }

        /// <summary>
        /// Retrieves the packet's data.
        /// </summary>
        /// <returns>The packet's data string.</returns>
        public override string CreateOutput() {
            // start with our exception
            Exception x = _Exception;
            StringBuilder Output = new StringBuilder();
            string Indent = "";

            // check whether we're doing messages or everything
            if (_MessagesOnly) {
                Output.Append("An error has occurred.");
                while (x != null) {
                    Output.Append(Environment.NewLine);
                    Output.Append(Indent);
                    Output.Append(x.Message);
                    Indent += "    ";
                    x = x.InnerException;
                }
            } else {
                Output.Append("An exception has occurred." + Environment.NewLine);

                // if this is a MemberAccessException, we can output some slightly more useful information
                if (x is MemberAccessException) {
                    Output.Append("This exception probably occurred because a module or plugin tried to use functionality that only exists in later versions of Black Lightning." + Environment.NewLine);
                    Output.Append("Check to make sure that the version of the plugin you're using is compatible with your current version of Black Lightning." + Environment.NewLine);
                    Output.Append("You can use the '@about' and '@about [plugin]' commands for version information about Black Lightning and the plugin in question, respectively." + Environment.NewLine);
                    Output.Append("It is recommended that you unload the module and/or plugin that caused this exception, because it may no longer be in a stable state." + Environment.NewLine);
                }

                // loop through the exception and all its inner exceptions
                while (x != null) {
                    // split the stack into lines
                    string Stack = x.StackTrace.Replace(Environment.NewLine, "\n");
                    string[] Split = Stack.Split('\n');

                    // find the first function call on the stack
                    int SplitIndex = 0;
                    while (!Split[SplitIndex].StartsWith("  ")) {
                        ++SplitIndex;
                    }

                    // parse out the method and filename/line from the first line
                    int AtIndex = Split[SplitIndex].IndexOf("at ");
                    int InIndex = Split[SplitIndex].IndexOf(" in ");
                    string Method = "";
                    string FileLine = "";
                    if (InIndex >= 0) {
                        Method = Split[SplitIndex].Substring(AtIndex + "at ".Length, InIndex - AtIndex - "at ".Length);
                        FileLine = Split[SplitIndex].Substring(InIndex + " in ".Length);
                    } else {
                        Method = Split[SplitIndex].Substring(AtIndex + "at ".Length);
                        FileLine = "Unknown";
                    }

                    // output the data for this exception
                    Output.Append(Indent + "Type: " + x.GetType().Name + Environment.NewLine);
                    Output.Append(Indent + "Message: " + x.Message + Environment.NewLine);
                    Output.Append(Indent + "Method: " + Method + Environment.NewLine);
                    Output.Append(Indent + "Location: " + FileLine + Environment.NewLine);
                    //Output.Append(Indent + "Stack: " + x.StackTrace + Environment.NewLine);
                    if (x.InnerException != null) {
                        Output.Append(Indent + "Inner Exception:" + Environment.NewLine);
                    }

                    // move to the next inner exception and up the indent level
                    x = x.InnerException;
                    Indent += "    ";
                }
            }

            // return the message
            return Output.ToString();
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="P">The ExceptionPacket to create a copy of.</param>
        /// <param name="DeepCopyOrNextState">True to make a deep copy of the given packet, false to instead use the given packet as the new packet's previous state.</param>
        protected ExceptionPacket(ExceptionPacket P, bool DeepCopyOrNextState)
            : base(P, DeepCopyOrNextState) {
            _Exception = P._Exception;
        }

        /// <summary>
        /// Creates a deep copy of the packet.
        /// </summary>
        /// <returns>A deep copy of the packet.</returns>
        public override object Clone() {
            return new ExceptionPacket(this, true);
        }

        /// <summary>
        /// Creates a new ExceptionPacket that has this ExceptionPacket as its PreviousState.
        /// </summary>
        /// <returns>A new ExceptionPacket that has this one as its PreviousState.</returns>
        internal override BLPacket GetNextState() {
            return new ExceptionPacket(this, false);
        }
    }
}
